package com.richinfo.util;

import com.richinfo.exception.*;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class ReflectUtils {
	public static boolean isGetter(Method method) {
		if (isGetterName(method.getName()) && method.getParameterTypes().length == 0) {
			return true;
		}

		return false;
	}

	public static boolean isSetter(Method method) {
		if (isSetterName(method.getName()) && method.getParameterTypes().length == 1) {
			return true;
		}

		return false;
	}

	public static boolean isGetterName(String methodName) {
		if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))
				&& !methodName.equals("getClass")) {
			return true;
		}

		return false;
	}

	public static boolean isSetterName(String methodName) {
		if (methodName.startsWith("set") && Character.isUpperCase(methodName.charAt(3))) {
			return true;
		}

		return false;
	}

	public static Method getSetter(Class<?> entityClass, Method getter) throws NoSuchMethodException {
		if (!isGetter(getter)) {
			throw new CodeNotInRequirmentException(getter.getDeclaringClass().getName() + "." + getter.getName(),
					"传入的方法不是getter");
		}

		Method method = null;

		try {
			method = entityClass.getMethod(getter.getName().replaceFirst("get", "set"), getter.getReturnType());
		} catch (SecurityException e) {
			throw new ApiInvokeException("getSetterByGetter", entityClass.getSimpleName() + "." + getter.getName(),
					null, null, e);
		}

		return method;
	}

	/**
	 * @param methodName
	 *            name of getter/setter
	 */
	public static String getFiledName(String methodName) {
		if (!isGetterName(methodName) && !isSetterName(methodName)) {
			throw new CodeNotInRequirmentException(methodName, "getter/setter命名错误");
		}

		return StringUtils.uncapitalize(methodName.substring(3));
	}

	public static List<Method> getGetters(Class<?> entityClass) {
		List<Method> getters = new ArrayList<>();
		Method[] allMethods = entityClass.getMethods();

		for (Method method : allMethods) {
			if (isGetter(method)) {
				getters.add(method);
			}
		}

		return getters;
	}

	public static List<Method> getSetters(Class<?> entityClass) {
		List<Method> setters = new ArrayList<>();
		Method[] allMethods = entityClass.getMethods();

		for (Method method : allMethods) {
			if (isSetter(method)) {
				setters.add(method);
			}
		}

		return setters;
	}

	public static Method getGetter(Class<?> entityClass, String fieldName) throws NoSuchMethodException {
		return entityClass.getMethod("get" + StringUtils.capitalize(fieldName));
	}

	/**
	 * 通过源对象的getter与目标对象的setter进行数据复制.
	 * 
	 * @param resource
	 *            源对象
	 * @param targetClass
	 *            目标对象类型
	 * @return 目标对象
	 */
	public static <T> T copyData(Object resource, Class<T> targetClass) {
		Map<String, Object> valuableFieldMap = getValuableFieldMapFromPojo(resource);

		return getValuablePojo(targetClass, valuableFieldMap);
	}

	/**
	 * 通过源对象的getter与目标对象的setter进行数据复制.
	 * 
	 * @param resourcePojos
	 *            源对象列表
	 * @param targetClass
	 *            目标对象类型
	 * @return 目标对象列表
	 */
	public static <T> List<T> copyDatas(List<? extends Object> resourcePojos, Class<T> targetClass) {
		List<T> targets = new ArrayList<T>();

		for (Object obj : resourcePojos) {
			targets.add(copyData(obj, targetClass));
		}

		return targets;
	}

	/**
	 * 通过源对象的getter向map复制数据.
	 * 
	 * @param resource
	 *            源对象
	 * @return map
	 */
	public static Map<String, Object> copyData(Object resource) {
		return getValuableFieldMapFromPojo(resource);
	}
	
	/**
	 * 通过源对象的getter向map复制数据.
	 * 
	 * @param resource
	 *            源对象
	 * @return map
	 */
	public static Map<String, Object> copyData(Object resource, String ...fieldNames) {
		return copyData(resource, Arrays.asList(fieldNames));
	}
	
	/**
	 * 通过源对象的getter向map复制数据.
	 * 
	 * @param resource
	 *            源对象
	 * @return map
	 */
	public static Map<String, Object> copyData(Object resource, List<String> fieldNames) {
		return getValuableFieldMapFromPojo(resource, fieldNames);
	}

	/**
	 * 通过目标对象的setter从map复制数据.
	 * 
	 * @param resourceMap
	 *            源map
	 * @param targetClass
	 *            目标对象类型
	 * @return 目标对象
	 */
	public static <T> T copyDataMap2Pojo(Map<String, Object> resourceMap, Class<T> targetClass) {
		return getValuablePojo(targetClass, resourceMap);
	}

	/**
	 * 分别容纳父子对象的两个列表由于泛型的不同，视为两个不同类型的列表，无法直接赋值。 但实际上，容纳父对象的容器也能容纳子对象（反之不可）。
	 * 此方法可将容纳子对象的列表赋值给容纳父对象的列表。
	 * 
	 * @param children
	 *            容纳子对象的列表
	 *            父对象类型
	 * @return
	 */
	public static <T> List<T> copyDatas4Extending(List<? extends T> children) {
		List<T> target = new ArrayList<T>();

		for (T t : children) {
			target.add(t);
		}

		return target;
	}

	public static void copyData(Object src, Object target) {
		Map<String, Object> srcFiledValues = ReflectUtils.copyData(src);

		for (String fieldName : srcFiledValues.keySet()) {
			Method setter = null;
			Method getter = null;
			Object fieldValue = srcFiledValues.get(fieldName);

			try {
				getter = ReflectUtils.getGetter(target.getClass(), fieldName);

				setter = getSetter(target.getClass(), fieldName, getter.getReturnType());

				setter.invoke(target, fieldValue);
			} catch (NoSuchMethodException e) {
				continue;
			} catch (Exception e) {
				throw new CodeNotInRequirmentException(target.getClass().getName(), setter.getName());
			}
		}
	}

	/**
	 * 获取实例类字段值. 支持实例类（user.id）、容器类(user.role[0].id)字段
	 * 
	 * @param entity
	 * @param fieldName
	 *            支持user.id格式
	 * @return
	 * @throws NoSuchFieldException
	 */
	public static Object getFieldValue(Object entity, String fieldName) throws NoSuchFieldException {
		Object value = null;
		Method method = null;
		String topFieldName = null; // 当前获取的字段名
		String newFieldName = null; // 继续嵌套获取的字段名
		Boolean isContainer = false; // 是否需要从Container中获取字段值
		Boolean isEntityAMap = false; // 是否需要从Map中获取字段值
		Boolean isNested = false; // 是否需要嵌套获取字段值
		int index = -1; // 列表/数组下标

		try {
			isNested = fieldName.contains(".");
			isEntityAMap = entity instanceof Map;

			if (isNested) {
				topFieldName = fieldName.split("\\.")[0];
			} else {
				topFieldName = fieldName;
			}

			if (topFieldName.contains("[")) {
				isContainer = true;

				index = Integer
						.valueOf(topFieldName.substring(topFieldName.indexOf("[") + 1, topFieldName.indexOf("]")));

				topFieldName = topFieldName.split("\\[")[0];
			}

			if (isEntityAMap) {
				value = ((Map<?, ?>) entity).get(topFieldName);
			} else {
				method = getGetter(entity.getClass(), topFieldName);

				value = method.invoke(entity);
			}

			if (isContainer) {
				if (value.getClass().isArray()) {
					value = ((Object[]) value)[index];
				} else {
					value = ((List<?>) value).get(index);
				}
			}

			if (isNested) {
				newFieldName = fieldName.replaceFirst(topFieldName + (isContainer ? ("\\[" + index + "\\].") : "."),
						"");

				return getFieldValue(value, newFieldName);
			} else {
				return value;
			}
		} catch (NoSuchMethodException e) {
			throw new NoSuchFieldException(fieldName);
		} catch (Exception e) {
			throw new BaseRuntimeException(e);
		}
	}

	public static String getFieldValueAsString(Object entity, String fieldName) throws NoSuchFieldException {
		Object fieldValue = getFieldValue(entity, fieldName);

		return fieldValue == null ? null : String.valueOf(fieldValue);
	}

	public static List<Object> getFieldsValue(Object entity, List<String> fieldNames) throws NoSuchFieldException {
		List<Object> values = new ArrayList<Object>();

		for (String fieldName : fieldNames) {
			values.add(getFieldValue(entity, fieldName));
		}

		return values;
	}

	public static List<String> getFieldsValueAsString(Object entity, List<String> fieldNames)
			throws NoSuchFieldException {
		List<String> values = new ArrayList<String>();

		for (String fieldName : fieldNames) {
			values.add(getFieldValueAsString(entity, fieldName));
		}

		return values;
	}

	public static List<Object> getBatchFieldValue(List<?> entities, String fieldName) throws NoSuchFieldException {
		List<Object> batchFieldValues = new ArrayList<Object>();

		for (Object entity : entities) {
			batchFieldValues.add(getFieldValue(entity, fieldName));
		}

		return batchFieldValues;
	}

	public static List<String> getBatchFieldValueAsString(List<?> entities, String fieldName)
			throws NoSuchFieldException {
		List<String> batchFieldValues = new ArrayList<String>();

		for (Object entity : entities) {
			batchFieldValues.add(getFieldValueAsString(entity, fieldName));
		}

		return batchFieldValues;
	}

	public static List<Object[]> getBatchFieldsValue(List<?> entities, List<String> fieldNames)
			throws NoSuchFieldException {
		List<Object[]> batchFieldValues = new ArrayList<Object[]>();

		for (Object entity : entities) {
			batchFieldValues.add(getFieldsValue(entity, fieldNames).toArray());
		}

		return batchFieldValues;
	}

	public static List<String[]> getBatchFieldsValueAsString(List<?> entities, List<String> fieldNames)
			throws NoSuchFieldException {
		List<String[]> batchFieldValues = new ArrayList<String[]>();

		for (Object entity : entities) {
			List<String> values = getFieldsValueAsString(entity, fieldNames);

			batchFieldValues.add(values.toArray(new String[values.size()]));
		}

		return batchFieldValues;
	}

	public static Method getSetter(Class<?> entityClass, String fieldName, Class<?> fieldType)
			throws NoSuchMethodException {
		return entityClass.getMethod("set" + StringUtils.capitalize(fieldName), fieldType);
	}

	public static String analyseFieldNameFromGetterSetter(String methodName) {
		return Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
	}
	
	public static void processData(Object pojo, pojoDataProcessor processor){
		List<Method> methods = getGetters(pojo.getClass());
		
		for (Method method : methods) {
			String methodName = method.getName();

			String fieldName = analyseFieldNameFromGetterSetter(methodName);
			Object fieldValue = null;

			try {
				fieldValue = method.invoke(pojo);
			} catch (Exception e) {
				throw new LackGetterOrSetterException(pojo.getClass().getSimpleName(), fieldName);
			}

			if (fieldValue != null) {
				try {
					setValueByField(pojo, fieldName, processor.processData(fieldValue));
				} catch (NoSuchMethodException e) {
					throw new LackGetterOrSetterException(pojo.getClass().getSimpleName(), fieldName);
				}
			}
		}

	}
	
	public static Map<String, Object> getValuableFieldMapFromPojo(Object pojo, List<String> fieldNames) {
		Map<String, Object> valuableFieldMap = new HashMap<String, Object>();
		List<Method> methods = getGetters(pojo.getClass());

		for (Method method : methods) {
			String methodName = method.getName();

			String fieldName = analyseFieldNameFromGetterSetter(methodName);
			
			if(!fieldNames.contains(fieldName)){
				continue;
			}
			
			Object fieldValue = null;

			try {
				fieldValue = method.invoke(pojo);
			} catch (Exception e) {
				throw new LackGetterOrSetterException(pojo.getClass().getSimpleName(), fieldName);
			}

			if (fieldValue != null && StringUtils.isNotBlank(String.valueOf(fieldValue))) {
				valuableFieldMap.put(fieldName, fieldValue);
			}
		}

		return valuableFieldMap;
	}
	
	public static Map<String, Object> getValuableFieldMapFromPojo(Object pojo) {
		Map<String, Object> valuableFieldMap = new HashMap<String, Object>();
		List<Method> methods = getGetters(pojo.getClass());

		for (Method method : methods) {
			String methodName = method.getName();

			String fieldName = analyseFieldNameFromGetterSetter(methodName);
			Object fieldValue = null;

			try {
				fieldValue = method.invoke(pojo);
			} catch (Exception e) {
				throw new LackGetterOrSetterException(pojo.getClass().getSimpleName(), fieldName);
			}

			if (fieldValue != null && StringUtils.isNotBlank(String.valueOf(fieldValue))) {
				valuableFieldMap.put(fieldName, fieldValue);
			}
		}

		return valuableFieldMap;
	}

	public static <T> T getValuablePojo(Class<T> pojoClass, Map<String, Object> fieldNameAndValueMap) {
		T pojo = null;

		try {
			pojo = pojoClass.newInstance();
		} catch (Exception e) {
			throw new TargetLackElementException(pojoClass.getName(), "公有的无参构造方法");
		}

		for (String fieldName : fieldNameAndValueMap.keySet()) {
			Method method = null;
			Object fieldValue = fieldNameAndValueMap.get(fieldName);

			try {
				method = getSetter(pojoClass, fieldName, fieldValue.getClass());

				method.invoke(pojo, fieldValue);
			} catch (NoSuchMethodException e) {
				continue;
			} catch (Exception e) {
				throw new CodeNotInRequirmentException(pojoClass.getName(), method.getName());
			}
		}

		return pojo;
	}

	/**
	 * 通过setter赋值.
	 * 
	 * @param targets
	 *            不可为null，可为空
	 * @param fieldName
	 * @param fieldValue
	 * @throws NoSuchMethodException
	 *             target必须要有对应的setter
	 */
	public static void setValue(List<?> targets, String fieldName, Object fieldValue) throws NoSuchMethodException {
		for (Object target : targets) {
			setValueByField(target, fieldName, fieldValue);
		}
	}

	/**
	 * 通过setter赋值.
	 * 
	 * @param target
	 *            不可为null
	 * @param getter
	 *            不可为null
	 * @throws NoSuchMethodException
	 *             target必须要有对应的setter
	 */
	public static void setValue(Object target, Method getter) throws NoSuchMethodException {
		Method targetSetter = ReflectUtils.getSetter(target.getClass(), getter);

		Object fieldValue = null;
		try {
			fieldValue = getter.invoke(target);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new CodeNotInRequirmentException(getter.getName());
		}

		setValue(target, targetSetter, fieldValue);
	}

	/**
	 * 通过setter赋值.
	 * 
	 * @param target
	 *            不可为null
	 * @param fieldName
	 * @param fieldValue
	 * @throws NoSuchMethodException
	 *             target必须要有对应的setter
	 */
	public static void setValueByField(Object target, String fieldName, Object fieldValue)
			throws NoSuchMethodException {
		Method setter = ReflectUtils.getSetter(target.getClass(), fieldName, fieldValue.getClass());

		setValue(target, setter, fieldValue);
	}

	public static void setValue(Object target, Method targetSetter, Object fieldValue) {
		try {
			targetSetter.invoke(target, fieldValue);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new ApiInvokeException("void setValue(Object target, Method targetSetter, Object fieldValue)",
					target.toString(), e);
		}
	}

	public static void clearFiledValue(Object entity, String fieldName) {
		Class<?> entityClass = entity.getClass();

		try {
			Field field = entityClass.getDeclaredField(fieldName);
			Class<?> fieldType = field.getType();
			Object emptyValue = null;

			if (fieldType.isPrimitive()) {
				if (fieldType.equals(boolean.class)) {
					emptyValue = false;
				} else {
					emptyValue = 0;
				}
			}

			Method method = getSetter(entityClass, fieldName, field.getType());
			method.invoke(entity, emptyValue);
		} catch (Exception e) {
			throw new LackGetterOrSetterException(entityClass.getSimpleName());
		}
	}
	
	public interface pojoDataProcessor{
		/**
		 * 
		 * @param value 数据
		 * @return 处理后的数据
		 */
		public Object processData(Object value);
	}

}
